#
# PREMISData.py
#
# PREMIS Models and custom properties for born-digital objects
# <http://www.loc.gov/standards/premis/v2/premis-2-0.pdf>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Copyright 2009 Jeremy Nelson, Tomichi Informatics
#
import datetime,os,logging
from basedata import *

from google.appengine.ext import db
from google.appengine.api import users

#------------------------------------------------------------------------------#
# PREMIS Properties
#------------------------------------------------------------------------------#
class agentIdentifier(object):
    """ The designation used to uniquely identify the agent within a
        preservation repository system. pg. 152 """

    # Controlled Vocabulary dictionary for types
    types = {None:0,
             'MARC Organization Codes':1,
             'URI':2,
             'DOI':3,
             'internal':4}

    reverse_types = {0: None,
                     1: 'MARC Organization Codes',
                     2: 'URI',
                     3: 'DOI',
                     4: 'internal'}

    def __init__(self,
                 type=None,
                 value=None):
        if agentIdentifier.types.has_key(type):
            self.type=agentIdentifier.types[type]
        else:
            raise db.BadValueError('''agentIdentifier.type %s not found''' %\
                                   type)
        if value is None:
            raise db.BadValueError('''agentIdentifier must have a value''')
        self.type,self.value = agentIdentifier.types[type],value
        

    def has_type(self):
        return self.type > 0

    def get_type(self):
        if self.type:
            return self.reverse_types[self.type]
        else:
            return None

    def __str__(self):
        return self.value

    def __not__(self):
        return (not (self.has_type() or
                     self.value))


class agentIdentifierProperty(db.Property):
    """ Datastore property for agentIdentifier """

    data_type = agentIdentifier

    def get_value_for_datastore(self,model_instance):
        agentIdentifier = super(agentIdentifierProperty,
                               self).get_value_for_datastore(model_instance)
        if agentIdentifier:
            return [agentIdentifier.type,agentIdentifier.value]
        else:
            return None

    def make_value_from_datastore(self,value):
        aType = agentIdentifier.reverse_types[value[0]]
        aValue = value[1]
        agentObj = agentIdentifier(type=aType,
                                   value=aValue)
        return agentObj

    def validate(self,value):
        if value is not None and not isinstance(value,
                                                agentIdentifier):
            raise db.BadValueError('Property %s must be convertable '
                                   'to an agentIdentifier instance (%s)',
                                   (self.name,value))
        return super(agentIdentifierProperty,self).validate(value)

    def empty(self,value):
        return not value

class eventIdentifier(object):
    """ A designation used to uniquely identify the event within the
        preservation repository system. pg. 141"""

    # Controlled vocabulary
    types = {None:0,
             'UUID':1,
             'internal':2,
             'CIDOCCRM':3}

    def __init__(self,
                 type=None,
                 value=None):
        if eventIdentifier.types.has_key(type):
            self.type=eventIdentifier.types[type]
        else:
            raise db.BadValueError('''eventIdentifier.type %s not found''',
                                   type)
        if value is None:
            raise db.BadValueError('''eventIdentifier must have a value''')
        self.type,self.value = eventIdentifier.types[type],value
        self.reverse_types = dict()
        for k,v in self.types.iteritems():
            self.reverse_types[v] = k

    def has_type(self):
        return self.type > 0

    def get_type(self):
        if self.type:
            return self.reverse_types[self.type]
        else:
            return None

    def __str__(self):
        return str(self.value)

    def __not__(self):
        return (not (self.value or
                     self.has_type()))

class eventIdentifierProperty(db.Property):
    """ Datastore property for eventIdentifier"""
    data_type = eventIdentifier

    def get_value_for_datastore(self,model_instance):
        eventIdentifier = super(eventIdentifierProperty,
                                self).get_value_for_datastore(model_instance)
        if eventIdentifier:
            return [eventIdentifier.type,eventIdentifier.value]
        else:
            return None
        
    def make_value_from_datastore(self,value):
        aType = value[0]
        aValue = value[1]
        eventObj = eventIdentifier(type=aType,
                                   value=aValue)
        return eventObj

    def validate(self,value):
        if value is not None and not isinstance(value,
                                                eventIdentifier):
            raise db.BadValueError('Property %s must be convertable '
                                   'to an eventIdentifier instance (%s)',
                                   (self.name,value))
        return super(eventIdentifierProperty,self).validate(value)

    def empty(self,value):
        return not value

class objectIdentifier(object):
    """ A designation used to uniquely identify the object within
        the preservation repository system in which it is stored. pg.35"""
    def __init__(self,
                 type=None,
                 value=None):
        if type is None or value is None:
            raise db.BadValueError("objectIdentifier requires a type and value")
        self.type,self.value = type,value

    def __str__(self):
        return str(self.value)

    def __not__(self):
        return (not (self.value or
                     self.type))

class objectIdentifierProperty(db.Property):
    """ Datastore property for objectIdentifier """
    data_type = objectIdentifier

    def get_value_for_datastore(self,model_instance):
        objectIdentifier = super(objectIdentifierProperty,
                                 self).get_value_for_datastore(model_instance)
        if objectIdentifier:
            return [objectIdentifier.type,objectIdentifier.value]
        else:
            return None

    def make_value_from_datastore(self,value):
        aType = value[0]
        aValue = value[1]
        objectObj = objectIdentifier(type=aType,
                                     value=aValue)
        return objectObj

    def validate(self,value):
        if value is not None and not isinstance(value,
                                                objectIdentifier):
            raise db.BadValueError('Property %s must be convertable '
                                   'to an objectIdentifier instance (%s)',
                                   (self.name,value))
        return super(objectIdentifierProperty,self).validate(value)

    def empty(self,value):
        return not value    

class rightsStatementIdentifier(object):
    """ The designation used to uniquely identify the rights statement
        within a preservation repository system. pg.160"""
    def __init__(self,
                 type=None,
                 value=None):
        if type is None or value is None:
            raise db.BadValueError("objectIdentifier requires a type and value")
        self.type,self.value = type,value

    def __str__(self):
        return str(self.value)

    def __not__(self):
        return (not (self.value or
                     self.type))

class rightsStatementIdentifierProperty(db.Property):
    """ Datastore property for objectIdentifier """
    data_type = objectIdentifier

    def get_value_for_datastore(self,model_instance):
        rightsStatmntIdentifier = super(rightsStatementIdentifierProperty,
                                 self).get_value_for_datastore(model_instance)
        if rightsStatmntIdentifier:
            return [rightsStatmntIdentifier.type,rightsStatmntIdentifier.value]
        else:
            return None

    def make_value_from_datastore(self,value):
        aType = value[0]
        aValue = value[1]
        rightsStatmntObj = rightsStatmentIdentifier(type=aType,
                                                   value=aValue)
        return rightsStatmntObj

    def validate(self,value):
        if value is not None and not isinstance(value,
                                                rightsStatementIdentifier):
            raise db.BadValueError('Property %s must be convertable '
                                   'to an rightsStatementIdentifier instance (%s)',
                                   (self.name,value))
        return super(rightsStatementIdentifierProperty,self).validate(value)

    def empty(self,value):
        return not value
    
class AgentRoles(object):
    """ Associates an agent with a role """
    roles = {None:0,
             "authorizer":1,
             "implementer":2,
             "validator":3,
             "executing program":4}

    def __init__(self,
                 agent=None,
                 role=None):
        if AgentRoles.roles.has_key(role):
            self.role = AgentRoles.roles[role]
        if agent is None:
            raise db.BadValueError('Agent is required for AgentRole')
        self.agent = agent
        self.reverse_roles = dict()
        for k,v in self.roles.iteritems():
            self.reverse_roles[v] = k

    def has_role(self):
        return self.role > 0

    def get_role(self):
        if self.role:
            return self.reverse_roles[self.role]
        else:
            return None

    def get_agent(self):
        if self.agent:
            return self.agent
        else:
            return None

    def __str__(self):
        return "%s: %s" % (self.get_role(),
                           self.get_agent().agentName)

    def __not__(self):
        return (not (self.has_role() or
                     self.agent))

class AgentRolesProperty(db.Property):
    """ Stores tuple of agent and role """
    data_type = AgentRoles

    def get_value_for_datastore(self,model_instance):
        AgentRoles = super(AgentRolesProperty,
                           self).get_value_for_datastore(model_instance)
        if AgentRoles:
            return [AgentRoles.agent,AgentRoles.role]
        else:
            return None

    def make_value_from_datastore(self,value):
        role = value[0]
        agent_key = value[1]
        agent = db.get(db.Key(agent_key))
        return AgentRoles(agent=agent,
                          role=role)

    def validate(self,value):
        if value is not None and not isinstance(value,
                                                AgentRoles):
            raise db.BadValueError('Property %s must be convertable '
                                   'to an AgentRoles instance (%s)',
                                   (self.name,value))
        return super(AgentRolesProperty,self).validate(value)

    def empty(self,value):
        return not value
    

class ObjectRolesList(object):
    """ A list of objects entities and associated roles.

        Each entity in the list contains an object entity and string with
        the role. No validation is performed on the role to confirm to a
        controlled vocabulary.
    """
    def __init__(self,
                 listing=None):
        self.listing = listing

    def __str__(self):
        output_list = list()
        for row in self.listing:
            output_list.append((row[0].value,
                                row[1]))
        return output_list

    def __not__(self):
        return (not self.listing)

class ObjectRolesListProperty(db.Property):
    """ Datastore property for ObjectRolesList """
    data_type = ObjectRolesList
    
    def get_value_for_datastore(self,model_instance):
        object_roles = super(ObjectRolesListProperty,
                             self).get_value_for_datastore(model_instance)
        if object_roles:
            output_list = list()
            for row in object_roles:
                output_list.append((row[0],row[1]))
            return ObjectRolesList(objectRolesList=output_list)
        else:
            return None

    def make_value_from_datastore(self,value):
        output = value
        return ObjectRolesList(objectRolesList=output)

    def validate(self,value):
        if value is not None and not isinstance(value,
                                                (ObjectRolesList,list)):
            raise db.BadValueError('Property %s must be convertable '
                                   'to an ObjectRolesList instance (%s)',
                                   (self.name,value))
        return super(ObjectRolesListProperty,
                     self).validate(value)

    def empty(self,value):
        return not value

class ObjectIdentifierList(object):
    """ Listing of object identifier objects """
    def __init__(self,
                 listing=None):
        for row in listing:
            if not isinstance(row,objectIdentifier):
                raise db.BadValueError('All list items must be objectIdentifier'
                                       ' %s is not an objectIdentifier.' %
                                       (row,))
        self.listing = listing

    def __str__(self):
        output_str = ""
        for row in self.listing:
            output_str += row[0].value
        return output_str

    def __not__(self):
        return (not self.listing)

class ObjectIdentifierListProperty(db.Property):
    """ Datastore property for ObjectIdentifierList """
    data_type = ObjectIdentifierList

    def get_value_for_datastore(self,model_instance):
        objects_ids = super(ObjectIdentifierListProperty,
                            self).get_value_for_datastore(model_instance)
        if objects_ids:
            return ObjectIdentifierList(listing=objects_ids.listing)
        else:
            return None

    def make_value_from_datastore(self,value):
        output = value
        return ObjectIdentifierList(listing=ouput)

    def validate(self,value):
        if value is not None and not isinstance(value,
                                                (ObjectIdentifierList,list)):
            raise db.BadValueError('Property %s must be convertable '
                                   'to an ObjectIdentifierList instance (%s)' %
                                   (self.name,value))
        return super(ObjectIdentifierListProperty,
                     self).validate(value)

    def empty(self,value):
        return not value

#------------------------------------------------------------------------------#
# PREMIS Models                                                                #
#------------------------------------------------------------------------------#
class AgentEntity(BaseModel):
    """ The Agent entity aggregates information about attributes or
        characteristics of agents (persons,organizations, or software)
        associated with rights management and preservation events in the life
        of a data object. Agent information serves to identify an agent
        unambiguously from all other Agent entities. (pg.158)"""
    agentIdentifier = agentIdentifierProperty(required=True)
    agentName = db.StringListProperty()
    agentType = db.StringProperty(default=None,
                                  choices=(['person',
                                            'organization',
                                            'software']))

class AgentRoles(db.Model):
    """ Supporting model for EventEntity, ObjectEntity,
        associates an agent with a role. """
    agent = db.ReferenceProperty(AgentEntity)
    role = db.StringProperty(required=True,
                             choices=(["authorizer",
                                       "implementer",
                                       "validator",
                                       "executing program"]))


class EventOutcomeDetails(db.Model):
    """ The EventOutcome Details contains detailed descriptions of the result
        or product of the event. pg. 140 """
    note = db.TextProperty()
    extensions = db.ListProperty(db.Key,
                                 default=None)

class EventEntity(BaseModel):
    """ The Event entity aggregates information about an action that involves
        one or more Object entities. Metadata about an Event would normally
        be recorded and stored separately from the digital object."""
    eventIdentifier = eventIdentifierProperty(required=True)
    eventType = db.StringProperty(required=True,
                                  choices=(["capture",
                                            "compression",
                                            "transmission time",
                                            "creation",
                                            "deaccession",
                                            "decompression",
                                            "decryption",
                                            "deletion",
                                            "digital signature validation",
                                            "dissemination",
                                            "fixity check",
                                            "ingestion",
                                            "message digest calculation",
                                            "migration",
                                            "normalization",
                                            "replication",
                                            "validation",
                                            "virus check"]))
    eventDateTime = db.ReferenceProperty(DateRangeModel,
                                         required=True)
    eventDetail = db.TextProperty()
    eventOutcome = db.StringProperty()
    # All listings should come from EventOutcomeDetails model
    eventOutcomeDetails = db.ListProperty(db.Key,
                                          default=None)
    linkingAgents = db.ListProperty(db.Key,
                                    default=None)
    linkingObjects = db.ListProperty(db.Key,
                                     default=None)
    role = db.StringListProperty()

class PreservationLevel(db.Model):
    """ Supporting model for ObjectEntity, records multiple preservationLevel
        information. Information indicating the decision or policy on the
        set of preservation functions to be applied to an object and the
        context in which the decision or policy was made. pg. 33 """
    value = db.StringProperty()
    role = db.StringProperty(default=None,
                             choices=(["requirement",
                                       "intention",
                                       "capability"]))
    rationale = db.StringProperty()
    dateAssigned = BaseDateProperty()

class SignificantProperties(db.Model):
    """ Characteristics of a particular object subjectively determined to be
        important to maintain through preservation actions. pg.39

        Used by ObjectEntity model.
        """
    type = db.StringProperty()
    value = db.StringProperty()
    extensions = db.ListProperty(db.Key,
                                 default=None)

class Fixity(db.Model):
    ''' Information used to verify whether an object has been altered in
        an undocumented or unauthorized way. pg. 47

        Used by ObjectCharacteristics model.'''
    messageDigestAlgorithm = db.StringProperty(choices=(["MD5",
                                                         "Adler-32",
                                                         "HAVAL",
                                                         "SHA-1",
                                                         "SHA-256",
                                                         "SHA-384",
                                                         "SHA-512",
                                                         "TIGER",
                                                         "WHIRLPOOL",
                                                         "internal"]))
    messageDigest = db.TextProperty(required=True)
    messageDigestOriginators = db.ListProperty(db.Key,
                                               default=None)
    

class Format(db.Model):
    """ Identification of the format of a file or bitstream where format is
        the organization of digital information according to preset
        specifications. pg. 53

        Used by ObjectCharacteristics model.
    """
    name = db.StringProperty(required=True)
    version = db.StringProperty()
    registryName = db.ListProperty(db.Key,
                                   default=None)
    registryKey = db.StringProperty()
    registryRole = db.StringProperty(default=None,
                                     choices=(["specificiation",
                                               "validation"]))
    note = db.StringProperty()
    
class CreatingApplication(db.Model):
    """ Information about the application that created the object. pg.63

        Used by ObjectCharacteristics model
    """
    name = db.StringProperty()
    version = db.StringProperty()
    dateCreated = BaseDateProperty()
    extensions = db.ListProperty(db.Key,
                                 default=None)
    
class Inhibitors(db.Model):
    """ Features of the object intended to inhibit access, use, or migration.
        pg.69

        Used by ObjectCharacteristics model.

    """
    type = db.StringProperty(choices=(["DES",
                                       "PGP",
                                       "Blowfish",
                                       "Password protected"]))
    target = db.StringProperty()
    inhibitor_key = db.StringProperty()
    
class ObjectCharacteristics(db.Model):
    """ Technical properties of a file or bitstream that are applicable to all
        or most formats. pg.45

        Used by ObjectEntity
    """
    compositionLevel = db.IntegerProperty()
    fixity = db.ListProperty(db.Key,
                             default=None)
    size = db.IntegerProperty()
    formats = db.ListProperty(db.Key,
                              default=None)
    creatingApplications = db.ListProperty(db.Key,
                                           default=None)
    inhibitors = db.ListProperty(db.Key,
                                 default=None)
    extensions = db.ListProperty(db.Key,
                                 default=None)
    
    
class Storage(db.Model):
    """ Information about how and where a file is stored in the storage
        system. pg. 75

        Used by ObjectEntity model
    """
    contentLocationType = db.StringProperty(default="GAE",
                                            choices=(["URI",
                                                      "hdl",
                                                      "DOI",
                                                      "NTFS",
                                                      "EXT3",
                                                      "GAE",
                                                      "AWS",
                                                      "byte offset"]))
    contentLocationValue = db.StringProperty()
    storageMedium = db.StringProperty(default="GAE",
                                      choices=(["Magnetic tape",
                                                "Hard disk",
                                                "TSM",
                                                "GAE",
                                                "AWS"]))

class Software(db.Model):
    """ Software required to render or use the object. pg. 89

        Used by Environment Model
    """
    swName = db.StringProperty()
    swVersion = db.StringProperty()
    swType = db.StringProperty(default=None,
                               choices=(["renderer",
                                         "ancillary",
                                         "operating system",
                                         "driver"]))
    swOtherInformation = db.TextProperty()
    swDependency = db.StringProperty()
    
    
    
class Hardware(db.Model):
    """Hardware components needed by the software referenced in
     Software model or the human user of the referenced software. pg.95

    Used by Environment
    """
    hwName = db.StringProperty()
    hwType = db.StringProperty(choices=(["processor",
                                         "memory",
                                         "input/output device",
                                         "storage device",
                                         "other"]))
    hwOtherInformation = db.StringListProperty(default=None)

class Environment(db.Model):
    """Hardware/software combinations supporting use of the object. pg.80

       Used by ObjectEntity model
    """
    characteristic = db.StringProperty(default=None,
                                       choices=(["unspecified",
                                                 "recommended",
                                                 "minimum",
                                                 "known to work"]))
    
    purposes = db.StringListProperty(default=None)
    note = db.TextProperty()
    dependencyName = db.StringProperty()
    dependencyIdentifier = objectIdentifierProperty()
    software = db.ListProperty(db.Key,
                               default=None)
    hardware = db.ListProperty(db.Key,
                               default=None)
    extensions = db.ListProperty(db.Key,
                                 default=None)
    
    
class Signature(db.Model):
    """Information needed to use a digital signature to authenticate the
       signer of an object and/or the information contained in the object.
       pg.101

       Used by ObjectEntity
    """
    encoding = db.StringProperty(required=True)
    signers = db.ListProperty(db.Key,
                              default=None)
    method = db.StringProperty(choices=(["DSA-SHA1",
                                         "RSA-SHA1",
                                         "RSA-MD5"]))
    value = db.TextProperty(required=True)
    validationRules = db.StringListProperty()
    properties_of = db.StringListProperty(default=None)
    keyInformation = db.ListProperty(db.Key,
                                     default=None)
    InformationExtensions = db.ListProperty(db.Key,
                                            default=None)
    
class RelatedObjectIdentification(db.Model):
    """ The identifier and sequential context of the related resource.
        pg. 113 """
    objectID = objectIdentifierProperty()
    sequence = db.StringProperty()

class RelatedEventsIdentification(db.Model):
    """ The identifier and contextual sequence of an event associated with
        the relationship. pg.117 """
    eventID = eventIdentifierProperty()
    sequence = db.StringProperty()
    
class Relationship(db.Model):
    """Information about a relationship between this object and one or
       more other objects. pg.110 """
    type_of = db.StringProperty(choices=(["structural",
                                          "derivation"]))
    subType = db.StringProperty(choices=(["has sibling",
                                          "is part of",
                                          "has part",
                                          "is source of",
                                          "has source",
                                          "has root",
                                          "includes",
                                          "is included in"]))
    relatedObjects = db.ListProperty(db.Key,
                                     default=None)
    relatedEvents = db.ListProperty(db.Key,
                                    default=None)
    
    
class ObjectIdentifiers(db.Model):
    """ Supporting model for ObjectEntity models """
    objectID = objectIdentifierProperty()

class ObjectRoles(db.Model):
    """ Supporting model for EventEntity, AgentEntity,
        and ObjectEntity classes """
    objectEntities = db.ListProperty(db.Key,
                                     default=None)
    
class ObjectEntity(BaseModel):
    """ The Object entity aggregates information about a digital object
        held by a preservation repository and describes those
        characteristics relevant to preservation management. pg. 32 """
    objectIdentifiers = db.ListProperty(db.Key,
                                        default=None)
    objectCategory = db.StringProperty(default=None,
                                       choices=(["representation",
                                                 "file",
                                                 "bitstream"]))
    preservationLevels = db.ListProperty(db.Key,
                                         default=None)
    significantProperties = db.ListProperty(db.Key,
                                            default=None)
    objectCharacteristics = db.ListProperty(db.Key,
                                            default=None)
    originalName = db.StringProperty()
    storage = db.ListProperty(db.Key,
                              default=None)
    environments = db.ListProperty(db.Key,
                                   default=None)
    signatureInformation = db.ListProperty(db.Key,
                                           default=None)
    relationships = db.ListProperty(db.Key,
                                    default=None)
    linkingEvents = db.ListProperty(db.Key,
                                    default=None)
    linkingIntellectualEntities = db.ListProperty(db.Key,
                                                  default=None)
    linkingRightsStatements = db.ListProperty(db.Key,
                                              default=None)

    def get_identifiers(self):
        return self.get_collection(property_name='objectIdentifiers')

class StatueInformation(db.Model):
    """ Information about the statute allowing use of the object.
        pg.175

        Used by Rights Entity model.
    """
    jurisdiction = db.ListProperty(db.Key,
                                   default=None)
    citation = db.StringProperty()
    determinationDate = BaseDateProperty()
    note = db.TextProperty()

class RightsGranted(db.Model):
    """ The action(s) that the granting agency has allowed the repository.
        pg. 180

        Used by Rights Entity model.
    """
    act = db.StringProperty(required=True,
                            choices=(["replicate",
                                      "migrate",
                                      "modify",
                                      "use",
                                      "disseminate",
                                      "delete"]))
    restrictions = db.StringListProperty()
    termOfGrant = db.ReferenceProperty(DateRangeModel,
                                       required=True)
    rightsGrantedNote = db.TextProperty()
    
class RightsEntity(BaseModel):
    """ Rights are entitlements allowed to agents by copyright or other
        intellectual property law. Permissions are powers or privileges
        granted by agreement between a rightsholder and another party or
        parties. pg.157 """
    rightsStatementID = rightsStatementIdentifierProperty()
    rightsBasis = db.StringListProperty(default=None)
    copyrightStatus = db.StringProperty(required=False,
                                        choices=(["copyrighted",
                                                  "publicdomain",
                                                  "unknown"]))
    copyrightJurisdiction = db.ListProperty(db.Key,
                                            default=None)
    copyrightStatusDeterminationDate = BaseDateProperty()
    copyrightNote = db.TextProperty()
    licenseIdentifierType = db.StringProperty()
    licenseIdentifierValue = db.StringProperty()
    licenseTerms = db.TextProperty()
    licenseNote = db.TextProperty()
    statues = db.ListProperty(db.Key,
                              default=None)
    rightsGranted = db.ListProperty(db.Key,
                                    default=None)
    linkingObjects = db.ListProperty(db.Key,
                                     default=None)
    linkingAgents = db.ListProperty(db.Key,
                                    default=None)
    rightsExtension = db.ListProperty(db.Key,
                                      default=None)
    
    
    
    
    
    
    

    
    
